#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.Text;

using Osh.Configuration;
using Osh.Dvb;
using Osh.Epg;
using Osh.Teletext;

namespace Osh.Media
{
	public class MediaController
	{
		private bool _started;
		private IOsh _osh;
		private List<IMediaPlayer> _mediaPlayers = new List<IMediaPlayer>();
		private IMediaPlayer _activeMediaPlayer;
		private EpgParser _epgParser = new EpgParser();
		private TeletextParser _teletextParser = new TeletextParser();
		private MediaLibraryNavigator _navigator = new MediaLibraryNavigator();

		public MediaController(IOsh osh)
		{
			_osh = osh;
			_osh.ModeChanged += new EventHandler<Osh.ModeChangeEventArgs>(Osh_ModeChanged);
			_osh.ModeChanging += new EventHandler<Osh.ModeChangeEventArgs>(Osh_ModeChanging);
			Config.Current.Changed += new ConfigurationEventHandler(Configuration_Changed);
		}

		private void Configuration_Changed(object sender, EventArgs args)
		{
			SyncProgramScreenshotProvider();
			SyncEpgParser();
			SyncTeletextParser();
		}

		public void Start()
		{
			if(_started)
				return;

			lock(this)
			{
				StartMediaPlayers();
				CreateMediaLibraries();
				_started = true;
			}
		}

		public void Stop()
		{
			if(!_started)
				return;

			lock(this)
			{
				StopMediaPlayers();
				_activeMediaPlayer = null;
				if(_epgParser.IsRunning)
					_epgParser.Stop();
				if(_teletextParser.IsRunning)
					_teletextParser.Stop();
				_started = false;
			}
		}

		public IList<IMediaPlayer> MediaPlayers
		{
			get { return _mediaPlayers; }
		}

		public EpgParser EpgParser
		{
			get { return _epgParser; }
		}

		public TeletextParser TeletextParser
		{
			get { return _teletextParser; }
		}

		public IMediaPlayer ActiveMediaPlayer
		{
			get { return _activeMediaPlayer; }
			private set
			{
				if(_activeMediaPlayer == value)
					return;

				if(value == null)
				{
					_activeMediaPlayer = value;
					UnregisterActiveMediaPlayer();
					OnActiveMediaPlayerChange(value);
				}
				else
				{
					if(!_mediaPlayers.Contains(value))
						throw new ArgumentOutOfRangeException("value");
					_activeMediaPlayer = value;
					RegisterActiveMediaPlayer(value);
					OnActiveMediaPlayerChange(value);
				}
				SyncProgramScreenshotProvider();
				SyncEpgParser();
				SyncTeletextParser();
			}
		}

		public MediaLibraryNavigator MediaLibraryNavigator
		{
			get { return _navigator; }
		}

		public bool IsMediaPlayerAvailable(string mediaPlayerName)
		{
			foreach(IMediaPlayer player in _mediaPlayers)
				if(player.Name == mediaPlayerName)
					return true;
			return false;
		}

		public IMediaPlayer GetMediaPlayerByName(string mediaPlayerName)
		{
			foreach(IMediaPlayer player in _mediaPlayers)
				if(player.Name == mediaPlayerName)
					return player;

			throw new ArgumentOutOfRangeException("mediaPlayerName");
		}

		public IMediaPlayer GetMediaPlayerByType(Type mediaPlayerType)
		{
			foreach(IMediaPlayer player in _mediaPlayers)
				if(player.GetType() == mediaPlayerType)
					return player;

			throw new ArgumentOutOfRangeException("mediaPlayerType");
		}

		private void StartMediaPlayers()
		{
			foreach(PlayerElement playerElement in Config.Current.Osh.Players)
			{
				if(!playerElement.Enabled)
					continue;

				Type playerType = Type.GetType(playerElement.Type, false);
				if(playerType == null)
					throw new TypeLoadException(string.Format("Player type '{0}' not found.", playerElement.Type));
				
				try
				{
					IMediaPlayer player = (IMediaPlayer)Activator.CreateInstance(playerType, new object[] { playerElement.Name });
					_mediaPlayers.Add(player);
					OshHostBase.Current.Services.RegisterService(playerType, player);

					player.Start += new EventHandler(MediaPlayer_Start);
					player.Exit += new EventHandler(MediaPlayer_Exit);
				}
				catch(System.Reflection.TargetInvocationException ie)
				{
					if(ie.InnerException is PlayerNotFoundException)
					{
						Log.Warning(Config.Current.MediaTraceSource, ie.InnerException.Message);
					}
					else
					{
						Log.Exception(Config.Current.MediaTraceSource, ie.InnerException,
							"Player '{0}' cannot be started.", playerElement.Type);
					}
				}
			}
		}

		private void StopMediaPlayers()
		{
			foreach(IMediaPlayer player in _mediaPlayers)
			{
				player.Start -= new EventHandler(MediaPlayer_Start);
				player.Exit -= new EventHandler(MediaPlayer_Exit);
				OshHostBase.Current.Services.UnregisterService(player.GetType());
			}
			_mediaPlayers.Clear();
		}

		private void CreateMediaLibraries()
		{
			foreach(MediaLibraryElement mediaLibraryElement in Config.Current.Osh.MediaLibraries)
			{
				Type mediaLibraryType = Type.GetType(mediaLibraryElement.Type, false);
				if(mediaLibraryType == null)
					throw new TypeLoadException(string.Format("Media library type '{0}' not found.", mediaLibraryElement.Type));
				
				IMediaLibraryFactory mediaLibraryFactory = (IMediaLibraryFactory)Activator.CreateInstance(mediaLibraryType);
				IMediaLibrary mediaLibrary = mediaLibraryFactory.CreateMediaLibrary(mediaLibraryElement.InitializeData);

				RegisterMediaLibrary(mediaLibrary);
			}
		}

		public void RegisterMediaLibrary(IMediaLibrary library)
		{
			if(!_navigator.Libraries.Contains(library))
			{
				_navigator.Libraries.Add(library);
				if(_navigator.Libraries.Count == 1)
					_navigator.MoveToFirstTree();
			}
		}

		public void UnregisterMediaLibrary(IMediaLibrary library)
		{
			if(_navigator.Libraries.Contains(library))
			{
				bool movedForward = false;
				bool movedBackward = false;
				if(_navigator.CurrentLibrary == library && _navigator.Libraries.Count > 1)
				{
					MediaLibraryNavigator clone = _navigator.Clone();
					if(clone.MoveToNextLibrary())
						if(clone.CurrentTree == null)
							if(clone.MoveToNextTree())
								movedForward = true;

					if(!movedForward)
					{
						clone = _navigator.Clone();
						if(clone.MoveToPreviousLibrary())
							if(!clone.MoveToLastLibraryTree())
								if(clone.MoveToPreviousTree())
									movedBackward = true;
					}
				}
				_navigator.Libraries.Remove(library);
				if(!movedForward && !movedBackward)
					_navigator.MoveToFirstTree();
			}
		}

		private void RegisterActiveMediaPlayer(IMediaPlayer mediaPlayer)
		{
			OshHostBase.Current.Services.RegisterService(typeof(IMediaPlayer), mediaPlayer);

			if(mediaPlayer is IDvbPlayer)
				OshHostBase.Current.Services.RegisterService(typeof(IDvbPlayer), mediaPlayer);
		}

		private void UnregisterActiveMediaPlayer()
		{
			OshHostBase.Current.Services.UnregisterService(typeof(IMediaPlayer));
			OshHostBase.Current.Services.UnregisterService(typeof(IDvbPlayer));
		}

		private void SyncProgramScreenshotProvider()
		{
			IMediaPlayer mediaPlayer = this.ActiveMediaPlayer;
			if(mediaPlayer == null)
				return;

			IProgramScreenshotProvider psp = mediaPlayer.Services.GetService<IProgramScreenshotProvider>();
			if(psp == null)
				return;

			psp.Enabled = Config.Current.Osh.Playback.CaptureScreenshots;
		}

		private void SyncEpgParser()
		{
			IMediaPlayer mediaPlayer = this.ActiveMediaPlayer;
			lock(_epgParser)
			{
				if(_epgParser.IsRunning)
				{
					if(_epgParser.Source != mediaPlayer || !Config.Current.Osh.Epg.Enabled)
						_epgParser.Stop();
				}

				if(mediaPlayer is IEpgStreamSource)
				{
					if(!_epgParser.IsRunning && Config.Current.Osh.Epg.Enabled)
						_epgParser.Start((IEpgStreamSource)mediaPlayer);
				}
			}
		}

		private void SyncTeletextParser()
		{
			IMediaPlayer mediaPlayer = this.ActiveMediaPlayer;
			lock(_teletextParser)
			{
				if(_teletextParser.IsRunning)
				{
					if(_teletextParser.Source != mediaPlayer || !Config.Current.Osh.Teletext.Enabled)
						_teletextParser.Stop();
				}

				if(mediaPlayer is ITeletextStreamSource)
				{
					if(!_teletextParser.IsRunning && Config.Current.Osh.Teletext.Enabled)
						_teletextParser.Start((ITeletextStreamSource)mediaPlayer);
				}
			}
		}

		private void MediaPlayer_Start(object sender, EventArgs e)
		{
			if(this.MediaPlayerStart != null)
			{
				try
				{
					this.MediaPlayerStart(this, new MediaPlayerEventArgs((IMediaPlayer)sender));
				}
				catch { }
			}
			UpdateMode((IMediaPlayer)sender, true);
		}

		private void MediaPlayer_Exit(object sender, EventArgs e)
		{
			if(this.MediaPlayerExit != null)
			{
				try
				{
					this.MediaPlayerExit(this, new MediaPlayerEventArgs((IMediaPlayer)sender));
				}
				catch { }
			}
			UpdateMode((IMediaPlayer)sender, false);
		}

		private void UpdateMode(IMediaPlayer mediaPlayer, bool isStarting)
		{
			if(!Config.Current.Osh.AutoSwitchMode)
				return;

			Dictionary<PlayerState, IMediaPlayer> waitingPlayers = new Dictionary<PlayerState, IMediaPlayer>();
			foreach(IMediaPlayer player in _mediaPlayers)
			{
				if(player != mediaPlayer)
					waitingPlayers[player.State] = player;
			}
			// If the player is just starting, then it has priority over other players with the same state (most likely Ready).
			if(isStarting)
				waitingPlayers[mediaPlayer.State] = mediaPlayer;

			// Select the most suitable player.
			if(waitingPlayers.ContainsKey(PlayerState.Playing))
				mediaPlayer = waitingPlayers[PlayerState.Playing];
			else if(waitingPlayers.ContainsKey(PlayerState.Paused))
				mediaPlayer = waitingPlayers[PlayerState.Paused];
			else if(waitingPlayers.ContainsKey(PlayerState.Ready))
				mediaPlayer = waitingPlayers[PlayerState.Ready];
			else
				mediaPlayer = null; // Switch to the mode withour a player.

			string mediaPlayerName = (mediaPlayer != null ? mediaPlayer.Name : string.Empty);
			ModeElement[] modeElements = Config.Current.Osh.Modes.GetModesByPlayerName(mediaPlayerName);

			if(modeElements.Length > 0)
				_osh.Mode = modeElements[0].Name;
			else
				Log.Error(Config.Current.MediaTraceSource,
					"Cannot find OSH mode by media player name '{0}'.", mediaPlayerName);
		}

		private void Osh_ModeChanging(object sender, Osh.ModeChangeEventArgs e)
		{
			ModeElement modeElement;
			if(!Config.Current.Osh.Modes.TryGetModeByName(e.Mode, out modeElement))
				e.Cancel = true;

			if(!string.IsNullOrEmpty(modeElement.Player) && !IsMediaPlayerAvailable(modeElement.Player))
			{
				Log.Warning(Config.Current.MediaTraceSource,
					"Osh mode '{0}' cannot be set. Media player '{0}' not found.", modeElement.Name, modeElement.Player);
				e.Cancel = true;
			}
		}

		private void Osh_ModeChanged(object sender, Osh.ModeChangeEventArgs e)
		{
			ModeElement modeElement;
			if(!Config.Current.Osh.Modes.TryGetModeByName(e.Mode, out modeElement))
				throw new InvalidOperationException(string.Format("OSH mode '{0}' not found.", e.Mode));

			Osh.Log.Info(Config.Current.MediaTraceSource, "Changing media player to '{0}'...", modeElement.Player);

			if(!string.IsNullOrEmpty(modeElement.Player))
			{
				IMediaPlayer mediaPlayer = GetMediaPlayerByName(modeElement.Player);
				this.ActiveMediaPlayer = mediaPlayer;
			}
			else
				this.ActiveMediaPlayer = null;
		}

		public void OnActiveMediaPlayerChange(IMediaPlayer mediaPlayer)
		{
			if(this.ActiveMediaPlayerChange != null)
				this.ActiveMediaPlayerChange(this, new MediaPlayerEventArgs(mediaPlayer));
		}

		public event EventHandler<MediaPlayerEventArgs> MediaPlayerStart;
		public event EventHandler<MediaPlayerEventArgs> MediaPlayerExit;
		public event EventHandler<MediaPlayerEventArgs> ActiveMediaPlayerChange;
	}
}
